home *** CD-ROM | disk | FTP | other *** search
- # Copyright © 2008-2009 Raphaël Hertzog <hertzog@debian.org>
- # Copyright © 2008 Frank Lichtenheld <djpig@debian.org>
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- package Dpkg::IPC;
-
- use strict;
- use warnings;
-
- our $VERSION = "1.00";
-
- use Dpkg::ErrorHandling;
- use Dpkg::Gettext;
-
- use base qw(Exporter);
- our @EXPORT = qw(spawn wait_child);
-
- =encoding utf8
-
- =head1 NAME
-
- Dpkg::IPC - helper functions for IPC
-
- =head1 DESCRIPTION
-
- Dpkg::IPC offers helper functions to allow you to execute
- other programs in an easy, yet flexible way, while hiding
- all the gory details of IPC (Inter-Process Communication)
- from you.
-
- =head1 METHODS
-
- =over 4
-
- =item spawn
-
- Creates a child process and executes another program in it.
- The arguments are interpreted as a hash of options, specifying
- how to handle the in and output of the program to execute.
- Returns the pid of the child process (unless the wait_child
- option was given).
-
- Any error will cause the function to exit with one of the
- Dpkg::ErrorHandling functions.
-
- Options:
-
- =over 4
-
- =item exec
-
- Can be either a scalar, i.e. the name of the program to be
- executed, or an array reference, i.e. the name of the program
- plus additional arguments. Note that the program will never be
- executed via the shell, so you can't specify additional arguments
- in the scalar string and you can't use any shell facilities like
- globbing.
-
- Mandatory Option.
-
- =item from_file, to_file, error_to_file
-
- Filename as scalar. Standard input/output/error of the
- child process will be redirected to the file specified.
-
- =item from_handle, to_handle, error_to_handle
-
- Filehandle. Standard input/output/error of the child process will be
- dup'ed from the handle.
-
- =item from_pipe, to_pipe, error_to_pipe
-
- Scalar reference or object based on IO::Handle. A pipe will be opened for
- each of the two options and either the reading (C<to_pipe> and
- C<error_to_pipe>) or the writing end (C<from_pipe>) will be returned in
- the referenced scalar. Standard input/output/error of the child process
- will be dup'ed to the other ends of the pipes.
-
- =item from_string, to_string, error_to_string
-
- Scalar reference. Standard input/output/error of the child
- process will be redirected to the string given as reference. Note
- that it wouldn't be strictly necessary to use a scalar reference
- for C<from_string>, as the string is not modified in any way. This was
- chosen only for reasons of symmetry with C<to_string> and
- C<error_to_string>. C<to_string> and C<error_to_string> imply the
- C<wait_child> option.
-
- =item wait_child
-
- Scalar. If containing a true value, wait_child() will be called before
- returning. The return value will of spawn() will be a true value,
- but not the pid.
-
- =item nocheck
-
- Scalar. Option of the wait_child() call.
-
- =item timeout
-
- Scalar. Option of the wait_child() call.
-
- =item chdir
-
- Scalar. The child process will chdir in the indicated directory before
- calling exec.
-
- =item env
-
- Hash reference. The child process will populate %ENV with the items of the
- hash before calling exec. This allows exporting environment variables.
-
- =item delete_env
-
- Array reference. The child process will remove all environment variables
- listed in the array before calling exec.
-
- =back
-
- =cut
-
- sub _sanity_check_opts {
- my (%opts) = @_;
-
- internerr("exec parameter is mandatory in spawn()")
- unless $opts{"exec"};
-
- my $to = my $error_to = my $from = 0;
- foreach (qw(file handle string pipe)) {
- $to++ if $opts{"to_$_"};
- $error_to++ if $opts{"error_to_$_"};
- $from++ if $opts{"from_$_"};
- }
- internerr("not more than one of to_* parameters is allowed")
- if $to > 1;
- internerr("not more than one of error_to_* parameters is allowed")
- if $error_to > 1;
- internerr("not more than one of from_* parameters is allowed")
- if $from > 1;
-
- foreach (qw(to_string error_to_string from_string)) {
- if (exists $opts{$_} and
- (!ref($opts{$_}) or ref($opts{$_}) ne 'SCALAR')) {
- internerr("parameter $_ must be a scalar reference");
- }
- }
-
- foreach (qw(to_pipe error_to_pipe from_pipe)) {
- if (exists $opts{$_} and
- (!ref($opts{$_}) or (ref($opts{$_}) ne 'SCALAR' and
- not $opts{$_}->isa("IO::Handle")))) {
- internerr("parameter $_ must be a scalar reference or an IO::Handle object");
- }
- }
-
- if (exists $opts{"timeout"} and defined($opts{"timeout"}) and
- $opts{"timeout"} !~ /^\d+$/) {
- internerr("parameter timeout must be an integer");
- }
-
- if (exists $opts{"env"} and ref($opts{"env"}) ne 'HASH') {
- internerr("parameter env must be a hash reference");
- }
-
- if (exists $opts{"delete_env"} and ref($opts{"delete_env"}) ne 'ARRAY') {
- internerr("parameter delete_env must be an array reference");
- }
-
- return %opts;
- }
-
- sub spawn {
- my (%opts) = _sanity_check_opts(@_);
- $opts{"close_in_child"} ||= [];
- my @prog;
- if (ref($opts{"exec"}) =~ /ARRAY/) {
- push @prog, @{$opts{"exec"}};
- } elsif (not ref($opts{"exec"})) {
- push @prog, $opts{"exec"};
- } else {
- internerr("invalid exec parameter in spawn()");
- }
- my ($from_string_pipe, $to_string_pipe, $error_to_string_pipe);
- if ($opts{"to_string"}) {
- $opts{"to_pipe"} = \$to_string_pipe;
- $opts{"wait_child"} = 1;
- }
- if ($opts{"error_to_string"}) {
- $opts{"error_to_pipe"} = \$error_to_string_pipe;
- $opts{"wait_child"} = 1;
- }
- if ($opts{"from_string"}) {
- $opts{"from_pipe"} = \$from_string_pipe;
- }
- # Create pipes if needed
- my ($input_pipe, $output_pipe, $error_pipe);
- if ($opts{"from_pipe"}) {
- pipe($opts{"from_handle"}, $input_pipe) ||
- syserr(_g("pipe for %s"), "@prog");
- ${$opts{"from_pipe"}} = $input_pipe;
- push @{$opts{"close_in_child"}}, $input_pipe;
- }
- if ($opts{"to_pipe"}) {
- pipe($output_pipe, $opts{"to_handle"}) ||
- syserr(_g("pipe for %s"), "@prog");
- ${$opts{"to_pipe"}} = $output_pipe;
- push @{$opts{"close_in_child"}}, $output_pipe;
- }
- if ($opts{"error_to_pipe"}) {
- pipe($error_pipe, $opts{"error_to_handle"}) ||
- syserr(_g("pipe for %s"), "@prog");
- ${$opts{"error_to_pipe"}} = $error_pipe;
- push @{$opts{"close_in_child"}}, $error_pipe;
- }
- # Fork and exec
- my $pid = fork();
- syserr(_g("cannot fork for %s"), "@prog") unless defined $pid;
- if (not $pid) {
- # Define environment variables
- if ($opts{"env"}) {
- foreach (keys %{$opts{"env"}}) {
- $ENV{$_} = $opts{"env"}{$_};
- }
- }
- if ($opts{"delete_env"}) {
- delete $ENV{$_} foreach (@{$opts{"delete_env"}});
- }
- # Change the current directory
- if ($opts{"chdir"}) {
- chdir($opts{"chdir"}) || syserr(_g("chdir to %s"), $opts{"chdir"});
- }
- # Redirect STDIN if needed
- if ($opts{"from_file"}) {
- open(STDIN, "<", $opts{"from_file"}) ||
- syserr(_g("cannot open %s"), $opts{"from_file"});
- } elsif ($opts{"from_handle"}) {
- open(STDIN, "<&", $opts{"from_handle"}) || syserr(_g("reopen stdin"));
- close($opts{"from_handle"}); # has been duped, can be closed
- }
- # Redirect STDOUT if needed
- if ($opts{"to_file"}) {
- open(STDOUT, ">", $opts{"to_file"}) ||
- syserr(_g("cannot write %s"), $opts{"to_file"});
- } elsif ($opts{"to_handle"}) {
- open(STDOUT, ">&", $opts{"to_handle"}) || syserr(_g("reopen stdout"));
- close($opts{"to_handle"}); # has been duped, can be closed
- }
- # Redirect STDERR if needed
- if ($opts{"error_to_file"}) {
- open(STDERR, ">", $opts{"error_to_file"}) ||
- syserr(_g("cannot write %s"), $opts{"error_to_file"});
- } elsif ($opts{"error_to_handle"}) {
- open(STDERR, ">&", $opts{"error_to_handle"}) || syserr(_g("reopen stdout"));
- close($opts{"error_to_handle"}); # has been duped, can be closed
- }
- # Close some inherited filehandles
- close($_) foreach (@{$opts{"close_in_child"}});
- # Execute the program
- exec({ $prog[0] } @prog) or syserr(_g("exec %s"), "@prog");
- }
- # Close handle that we can't use any more
- close($opts{"from_handle"}) if exists $opts{"from_handle"};
- close($opts{"to_handle"}) if exists $opts{"to_handle"};
- close($opts{"error_to_handle"}) if exists $opts{"error_to_handle"};
-
- if ($opts{"from_string"}) {
- print $from_string_pipe ${$opts{"from_string"}};
- close($from_string_pipe);
- }
- if ($opts{"to_string"}) {
- local $/ = undef;
- ${$opts{"to_string"}} = readline($to_string_pipe);
- }
- if ($opts{"error_to_string"}) {
- local $/ = undef;
- ${$opts{"error_to_string"}} = readline($error_to_string_pipe);
- }
- if ($opts{"wait_child"}) {
- my $cmdline = "@prog";
- if ($opts{"env"}) {
- foreach (keys %{$opts{"env"}}) {
- $cmdline = "$_=\"" . $opts{"env"}{$_} . "\" $cmdline";
- }
- }
- wait_child($pid, nocheck => $opts{"nocheck"},
- timeout => $opts{"timeout"}, cmdline => $cmdline);
- return 1;
- }
-
- return $pid;
- }
-
-
- =item wait_child
-
- Takes as first argument the pid of the process to wait for.
- Remaining arguments are taken as a hash of options. Returns
- nothing. Fails if the child has been ended by a signal or
- if it exited non-zero.
-
- Options:
-
- =over 4
-
- =item cmdline
-
- String to identify the child process in error messages.
- Defaults to "child process".
-
- =item nocheck
-
- If true do not check the return status of the child (and thus
- do not fail it it has been killed or if it exited with a
- non-zero return code).
-
- =item timeout
-
- Set a maximum time to wait for the process, after that fail
- with an error message.
-
- =back
-
- =cut
-
- sub wait_child {
- my ($pid, %opts) = @_;
- $opts{"cmdline"} ||= _g("child process");
- internerr("no PID set, cannot wait end of process") unless $pid;
- eval {
- local $SIG{ALRM} = sub { die "alarm\n" };
- alarm($opts{"timeout"}) if defined($opts{"timeout"});
- $pid == waitpid($pid, 0) or syserr(_g("wait for %s"), $opts{"cmdline"});
- alarm(0) if defined($opts{"timeout"});
- };
- if ($@) {
- die $@ unless $@ eq "alarm\n";
- error(ngettext("%s didn't complete in %d second",
- "%s didn't complete in %d seconds",
- $opts{"timeout"}),
- $opts{"cmdline"}, $opts{"timeout"});
- }
- unless ($opts{"nocheck"}) {
- subprocerr($opts{"cmdline"}) if $?;
- }
- }
-
- 1;
- __END__
-
- =back
-
- =head1 AUTHORS
-
- Written by Raphaël Hertzog <hertzog@debian.org> and
- Frank Lichtenheld <djpig@debian.org>.
-
- =head1 SEE ALSO
-
- Dpkg, Dpkg::ErrorHandling
-